var UBSync = {};

(function (UBSync) {

  'use strict';

  // @class moving average filter
  function MAF(coef) {
    this.value = 0.0;
    this.coef = (coef || 0.5);
  }

  MAF.prototype = {
    process: function (value) {
      this.value = this.coef * value + (1.0 - this.coef) * this.value;
    },
    getValue: function () {
      return this.value;
    }
  };


  // window.performance
  var _perf = window.performance;

  // audio context and a stream stater
  var _ctx = new webkitAudioContext();
  var _osc = _ctx.createOscillator();
  var _g = _ctx.createGain();
  _osc.start(0);
  _osc.stop(0.350);
  _osc.connect(_g);
  _g.gain.value = 0.0;
  _g.connect(_ctx.destination);


  // getters
  Object.defineProperties(UBSync, {
    context: {
      value: _ctx
    },
    // global (window.performance) now
    GNow: {
      get: function () {
        // NOTE: the separate division is for the maximum time resolution...
        return ((_perf.timing.navigationStart / 1000.0) + (_perf.now() / 1000.0));
      }
    },
    // local (audioContext) now
    LNow: {
      get: function () {
        return _ctx.currentTime;
      }
    }
  });


  /**
   * asnyc file loader from WAAX r12 core
   */
  UBSync.loadAudioFile = function (url, oncomplete) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.responseType = "arraybuffer";
    xhr.onprogress = function (event) {
      var pct = ~~((event.loaded / event.total) * 100) + '%';
      console.log('loading...' + pct);
    };
    xhr.onload = function () {
      try {
        var buffer = _ctx.createBuffer(xhr.response, false);
        oncomplete(buffer);
        console.log(url + ': loading complete.');
      } catch (error) {
        console.log('XHR failed (' + error.message + '): ' + url);
      }
    };
    xhr.send();
  };


  /**
   * stabilize with onstabilized callback
   */
  UBSync.stabilize = function (onstabilized, stabilizerCoef, verbose) {

    // coef, 2 moving average filters
    var _coef = (stabilizerCoef || 0.5);
    var _gMAF = new MAF(_coef);
    var _lMAF = new MAF(_coef);
    var _readyCount = 0;
    var _verbose = (verbose || false);

    var _prevGNow = UBSync.GNow;  // global now (from window.performance)
    var _prevLNow = UBSync.LNow;  // local now (from audio context)


    // monitor
    function _monitor(timestamp) {

      var gNow = UBSync.GNow,
          lNow = UBSync.LNow;
      _gMAF.process(gNow - _prevGNow);
      _lMAF.process(lNow - _prevLNow);
      _prevGNow = gNow;
      _prevLNow = lNow;

      var diff = _gMAF.getValue() - _lMAF.getValue();
      // if deviation is below 1 microsecond, add 1 to ready count
      if (diff < 0.000001) {
        _readyCount++;
      }
      // if deviation goes beyond 10ms, reset the count
      else if (diff > 0.010) {
        _readyCount = 0;
      }

      if (_readyCount < 30) {
        // if ready count is below than 30, the clock is not healthy
        // so iterate again.
        requestAnimationFrame(_monitor);
      } else {
        // stab complete
        if (_verbose) {
          console.log('[UBSync] stats:');
          console.log(' - stab elapsed = ' + timestamp.toFixed(3) + 'ms');
          console.log(' - avg global delta = ' + (_gMAF.getValue() * 1000).toFixed(3) + 'ms');
          console.log(' - avg local delta = ' + (_lMAF.getValue() * 1000).toFixed(3) + 'ms');
        }
        console.log('[UBSync] the clock is stabilized.');
        onstabilized();
      }
    }

    // boot up
    console.log('[UBSync] start stabilizing...');
    _monitor();

  };

})(UBSync);